package com.hockeo.server.rpc.classic;

import java.lang.reflect.InvocationTargetException;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.hockeo.server.rpc.AuthenticationType;
import com.hockeo.shared.VHUtil;
import com.hockeo.shared.rpc.classic.error.RpcNotAnAdminException;
import com.hockeo.shared.rpc.classic.error.RpcNotAuthenticatedException;
import com.hockeo.shared.rpc.classic.error.RpcServerException;

/**
 * Base RPC servlet to be implemented by all classic GWT/RPC remote service implementations.
 *
 * @version $Id$
 * @author jjanke
 */
public abstract class RpcBaseServlet extends RemoteServiceServlet
{
  private static final long serialVersionUID = 1L;

  /**
   * Overridden to manually process the GWT RPC call. This is necessary in order to make
   * checks (e.g. if the user is authenticated) prior to actually handling the request.
   */
  @Override
  public String processCall( String strPayLoad ) throws SerializationException
  {
    // further processing
    //
    try
    {
      // Decode the RPC request
      //
      RPCRequest rpcrq = RPC.decodeRequest( strPayLoad, getClass(), this );

      // Get RPC method options and enforce proper authentication
      //
      RpcMethodOptions opts = rpcrq.getMethod().getAnnotation( RpcMethodOptions.class );
      if ( opts != null )
        checkAuthentication( opts.authType() );

      Object objResult = rpcrq.getMethod().invoke( this, rpcrq.getParameters() );

      // Encode response for the client
      //
      return RPC.encodeResponseForSuccess( rpcrq.getMethod(), objResult, rpcrq.getSerializationPolicy() );
    }
    catch ( InvocationTargetException ex )
    {
      getServletContext().log( "Invocation target expection.", ex.getCause() );
      return RPC.encodeResponseForFailure( null, ex.getCause() );
    }
    catch ( Exception ex )
    {
      getServletContext().log( "Server side request processing failed.", ex );
      return RPC.encodeResponseForFailure( null, ex );
    }
  }

  /**
   * Checks whether the calling user must be authenticated. If this is the case and the
   * user is not authenticated an exception is thrown.
   *
   * @param authType the enforced authentication type
   * @throws RpcNotAuthenticatedException if authentication is required but the user is
   *           not authenticated
   * @throws RpcNotAnAdminException if the user is authenticated but not as an admin and
   *           the service only allows access to administrators
   */
  protected void checkAuthentication( AuthenticationType authType ) throws RpcServerException
  {
    if ( authType == null || authType == AuthenticationType.NONE )
      return;

    UserService userService = UserServiceFactory.getUserService();

    if ( !userService.isUserLoggedIn() )
      throw new RpcNotAuthenticatedException( userService.createLoginURL( VHUtil.URL_WELCOME_PAGE ) );

    if ( authType == AuthenticationType.ADMIN && !userService.isUserAdmin() )
      throw new RpcNotAnAdminException();
  }
}
